package com.ruoyi.user.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 雪花算法ID生成器实现
 * 64位ID结构：
 * 1位符号位 + 41位时间戳 + 5位数据中心ID + 5位机器ID + 12位序列号
 */
@Component
@Slf4j
public class SnowflakeIdGenerator {

    // 开始时间戳（2023-01-01 00:00:00）
    private final long START_TIMESTAMP = 1672531200000L;

    // 数据中心ID位数
    private final long DATA_CENTER_BITS = 5L;

    // 机器ID位数
    private final long WORKER_BITS = 5L;

    // 序列号位数
    private final long SEQUENCE_BITS = 12L;

    // 数据中心ID最大值
    private final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_BITS);

    // 机器ID最大值
    private final long MAX_WORKER_ID = ~(-1L << WORKER_BITS);

    // 序列号最大值
    private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);

    // 机器ID左移位数
    private final long WORKER_SHIFT = SEQUENCE_BITS;

    // 数据中心ID左移位数
    private final long DATA_CENTER_SHIFT = SEQUENCE_BITS + WORKER_BITS;

    // 时间戳左移位数
    private final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_BITS + DATA_CENTER_BITS;

    // 数据中心ID
    @Value("${id.generator.snowflake.data-center-id:1}")
    private long dataCenterId;

    // 机器ID
    @Value("${id.generator.snowflake.worker-id:1}")
    private long workerId;

    // 序列号
    private long sequence = 0L;

    // 上次生成ID的时间戳
    private long lastTimestamp = -1L;

    // 是否启用自动分配workerId
    @Value("${id.generator.snowflake.auto-assign-worker-id:true}")
    private boolean autoAssignWorkerId;

    @PostConstruct
    public void init() {
        // 校验数据中心ID和机器ID
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("数据中心ID不能大于 %d 或小于 0", MAX_DATA_CENTER_ID));
        }
        // 如果启用自动分配，则根据IP地址计算workerId
        if (autoAssignWorkerId) {
            workerId = generateWorkerIdFromIp();
            log.info("自动分配workerId: {}", workerId);
        }
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("机器ID不能大于 %d 或小于 0", MAX_WORKER_ID));
        }
        log.info("雪花算法ID生成器初始化成功，数据中心ID: {}, 机器ID: {}", dataCenterId, workerId);
    }

    /**
     * 根据IP地址生成workerId
     */
    private long generateWorkerIdFromIp() {
        try {
            InetAddress address = InetAddress.getLocalHost();
            byte[] ipBytes = address.getAddress();

            // 取IP地址的后两个字节作为workerId
            long workerId = ((ipBytes[ipBytes.length - 2] & 0xFF) << 8) | (ipBytes[ipBytes.length - 1] & 0xFF);

            // 确保workerId在有效范围内
            return workerId % (MAX_WORKER_ID + 1);
        } catch (UnknownHostException e) {
            log.warn("无法获取本地IP地址，使用默认workerId: 1");
            return 1;
        }
    }

    public Long nextId() {
        return nextId("default");
    }

    public Long nextId(String businessType) {
        synchronized (this) {
            return generateNextId();
        }
    }

    /**
     * 批量生成唯一ID
     *
     * @param count 生成数量
     * @return ID数组
     */
    public Long[] batchNextId(int count) {
        if (count <= 0) {
            throw new IllegalArgumentException("批量生成ID数量必须大于0");
        }

        synchronized (this) {
            Long[] ids = new Long[count];
            long currentTimestamp = getCurrentTimestamp();

            // 校验批量生成数量是否超过序列号最大值
            if (count > MAX_SEQUENCE + 1) {
                log.warn("批量生成ID数量超过序列号最大值，可能会导致性能问题，请求数量: {}, 最大支持数量: {}",
                        count, MAX_SEQUENCE + 1);
            }

            // 检查系统时钟回退
            if (currentTimestamp < lastTimestamp) {
                log.error("系统时钟回退，拒绝生成ID！当前时间戳: {}, 上次时间戳: {}", currentTimestamp, lastTimestamp);
                throw new RuntimeException(String.format("系统时钟回退，拒绝生成ID！当前时间戳: %d, 上次时间戳: %d", currentTimestamp, lastTimestamp));
            }

            // 保存当前批次的序列号起始值
            long startSequence = sequence;

            // 处理同一时间戳的情况
            if (currentTimestamp == lastTimestamp) {
                // 检查序列号是否足够
                if (sequence + count > MAX_SEQUENCE) {
                    // 如果序列号不够，等待下一个时间戳
                    currentTimestamp = waitNextTimestamp(lastTimestamp);
                    startSequence = 0L;
                }
            } else {
                // 不同时间戳，重置序列号起始值
                startSequence = 0L;
            }

            // 批量生成ID
            for (int i = 0; i < count; i++) {
                ids[i] = ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
                        | (dataCenterId << DATA_CENTER_SHIFT)
                        | (workerId << WORKER_SHIFT)
                        | (startSequence + i);
            }

            // 更新序列号和时间戳
            sequence = startSequence + count;
            lastTimestamp = currentTimestamp;

            return ids;
        }
    }

    /**
     * 生成单个ID的核心方法
     */
    private Long generateNextId() {
        long currentTimestamp = getCurrentTimestamp();

        // 如果当前时间戳小于上次生成ID的时间戳，说明系统时钟回退，抛出异常
        if (currentTimestamp < lastTimestamp) {
            log.error("系统时钟回退，拒绝生成ID！当前时间戳: {}, 上次时间戳: {}", currentTimestamp, lastTimestamp);
            throw new RuntimeException(String.format("系统时钟回退，拒绝生成ID！当前时间戳: %d, 上次时间戳: %d", currentTimestamp, lastTimestamp));
        }

        // 如果是同一时间戳，则序列号加1
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 如果序列号达到最大值，则等待下一个时间戳
            if (sequence == 0) {
                currentTimestamp = waitNextTimestamp(lastTimestamp);
            }
        } else {
            // 不同时间戳，序列号重置为0
            sequence = 0L;
        }

        // 更新上次生成ID的时间戳
        lastTimestamp = currentTimestamp;

        // 生成ID
        return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
                | (dataCenterId << DATA_CENTER_SHIFT)
                | (workerId << WORKER_SHIFT)
                | sequence;
    }

    /**
     * 获取当前时间戳（毫秒）
     */
    private long getCurrentTimestamp() {
        return System.currentTimeMillis();
    }

    /**
     * 等待下一个时间戳
     */
    private long waitNextTimestamp(long lastTimestamp) {
        long timestamp = getCurrentTimestamp();
        while (timestamp <= lastTimestamp) {
            timestamp = getCurrentTimestamp();
        }
        return timestamp;
    }
}